<?php


namespace XinSheng;

use RuntimeException;
use Fize\Math\Financial;

/**
 * 根据内部年化IRR推算外部租金
 */
class RentByIrr
{

    /**
     * @var float 转让价款
     */
    protected $leaseAmount;

    /**
     * @var float 首付款
     */
    protected $downPayment;

    /**
     * @var float 预付款
     */
    protected $deposit;

    /**
     * @var float 留购价
     */
    protected $residualValue;

    /**
     * @var float 手续费
     */
    protected $serviceAmount;

    /**
     * @var float 内部年化IRR
     */
    protected $interestRate;

    /**
     * @var int 期次
     */
    protected $issues;

    /**
     * @var array 其他费用
     */
    protected $otherfees;

    /**
     * @var int 每期月数
     */
    protected $issueMonths;

    /**
     * @var int 支付方式：0-期末支付；1-期初支付
     */
    protected $paymentMode;

    /**
     * @var float 每期利率
     */
    protected $issueRate;

    /**
     * @var int 保留小数位
     */
    protected $scale;

    /**
     * @var float 每期租金
     */
    protected $rent;

    /**
     * 初始化
     * @param float $leaseAmount   转让价款
     * @param float $downPayment   首付款
     * @param float $deposit       预付款
     * @param float $residualValue 留购价
     * @param float $serviceAmount 手续费
     * @param float $interestRate  内部年化IRR
     * @param int   $issues        期次
     * @param array $otherfees     其他费用
     * @param int   $paymentMode   支付方式：0-期末支付；1-期初支付
     * @param int   $issueMonths   每期月数
     * @param int   $scale         保留小数位
     */
    public function __construct(
        $leaseAmount, $downPayment, $deposit, $residualValue, $serviceAmount,
        $interestRate, $issues, $otherfees = [], $paymentMode = 0, $issueMonths = 1,
        $scale = 2)
    {
        $this->leaseAmount = $leaseAmount;
        $this->downPayment = $downPayment;
        $this->deposit = $deposit;
        $this->interestRate = $interestRate;
        $this->issues = $issues;
        $this->residualValue = $residualValue;
        $this->otherfees = $otherfees;
        $this->serviceAmount = $serviceAmount;
        $this->paymentMode = $paymentMode;
        $this->issueMonths = $issueMonths;

        $this->issueRate = $this->interestRate * $this->issueMonths / 12;
        $this->scale = $scale;
    }

    /**
     * 获取每期租金
     * @param int $maxiter 尝试次数
     * @return float
     */
    public function getRent($maxiter = 1000)
    {
        if (!is_null($this->rent)) {
            return $this->rent;
        }
        $fee0Amount = $this->getIssueOtherfeesAmount(0);
        $netAmount = round($this->leaseAmount - $this->downPayment - $this->deposit - $this->serviceAmount - $fee0Amount, $this->scale);  // 净融资额
        $rent = Financial::pmt($this->issueRate, $this->issues, -$netAmount, 0, $this->paymentMode);
        $peerNPV = $netAmount / $rent;

        $tryCount = 0;
        $oldRent = 0.00;
        while ($tryCount < $maxiter) {
            $cashflow = $this->getCashFlow($rent);
            $npv = Financial::npv($this->issueRate, $cashflow);
            $newRent = $rent - $npv / $peerNPV;
            if (round($newRent, $this->scale) == round($rent, $this->scale) || round($newRent, $this->scale) == round($oldRent, $this->scale)) {
                $this->rent = round($rent, $this->scale);
                return $this->rent;
            }
            $oldRent = $rent;
            $rent = $newRent;
            $tryCount++;
        }
        throw new RuntimeException('测算次数过多！');
    }

    /**
     * 获取现金流
     * @param float $rent 每期租金
     */
    protected function getCashFlow($rent)
    {
        $rent = round($rent, $this->scale);
        $cashflow = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            if ($i == 0) {
                $cashflow[0] = -round($this->leaseAmount - $this->downPayment - $this->deposit - $this->serviceAmount, $this->scale);  // -(转让价款 - 首付款 - 预付款 - 手续费 - 其他费用)
                if ($this->paymentMode == 1) {  // 期初支付
                    $cashflow[0] += $rent;
                }
            } else {
                $cashflow[$i] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $rent;  // 现金流 = 租金 - 预付抵扣 + 留购 + 其他费用
            }
        }
        // 预付抵扣
        $deposit = $this->deposit;
        $start = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
        for ($i = $start; $i > 0; $i--) {
            if ($deposit <= 0) {
                break;
            }
            if ($cashflow[$i] <= $deposit) {
                $deposit = round($deposit - $cashflow[$i], $this->scale);
                $cashflow[$i] = 0;
            } else {
                $cashflow[$i] = round($cashflow[$i] - $deposit, $this->scale);
                $deposit = 0;
            }
        }
        // 留购价
        if ($this->paymentMode == 1) {
            $cashflow[$this->issues] = $this->residualValue;
        } else {
            $cashflow[$this->issues] = round($cashflow[$this->issues] + $this->residualValue, $this->scale);
        }
        // 其他费用
        if (!empty($this->otherfees)) {
            foreach ($this->otherfees as $fee) {
                $cashflow[$fee['issue']] = round($cashflow[$fee['issue']] + $fee['amount'], $this->scale);
            }
        }
        return $cashflow;
    }

    /**
     * 获取指定期次的其他费用
     * @param int $issue 期次
     * @return array 其他费用数组
     */
    protected function getIssueOtherfees($issue)
    {
        $fees = [];
        foreach ($this->otherfees as $fee) {
            if ($fee['issue'] == $issue) {
                $fees[] = $fee;
            }
        }
        return $fees;
    }

    /**
     * 获取每期的其他费用金额
     * @param int $issue 期次
     * @return float
     */
    protected function getIssueOtherfeesAmount($issue)
    {
        $feeAmount = 0.00;
        foreach ($this->otherfees as $fee) {
            if ($fee['issue'] == $issue) {
                $feeAmount += $fee['amount'];
            }
        }
        return $feeAmount;
    }
}